home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / mxutil / tspak17 / snd2sam.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1994-09-11  |  44.9 KB  |  1,298 lines

  1. (* Snd2sam.pas - Convert DeskMate .snd to Amiga music module sample.
  2.    Version 1.1
  3.    Jeffrey L. Hayes
  4.    September 11, 1994
  5.  
  6.    This version has been modified to add support for new-format .snd files.
  7.  
  8.    This program converts a DeskMate Sound.pdm instrument or sound file to
  9.    one or more .sam files for use with Amiga .mod editors, particularly
  10.    ModEdit v.3.1.  The input .snd file must be uncompressed.  For each note
  11.    in the instrument file, two output files are created:  (1) a .sam file,
  12.    and (2) a .not (ASCII) file giving needed information about the .sam
  13.    file, including its pitch, large-scale tuning, transposition, and looping
  14.    parameters.  Since the .sam file is a headerless format, the user is
  15.    required to enter the information from the .not file manually when using
  16.    the sample in a .mod editor.
  17.  
  18.    The syntax is:
  19.  
  20.        SND2SAM <.snd file> [<directory>]
  21.  
  22.    The input .snd filename is required and may include drive and path.  If
  23.    no extension is specified, it defaults to .snd; a file without an
  24.    extension can by used by ending the name with a period.
  25.  
  26.    The second parameter, which is optional, is the directory where the
  27.    output .sam and .not files will be placed.  If not specified, it
  28.    defaults to the current directory.
  29.  
  30.    The output filenames are generated from the input .snd filename.  For
  31.    the first note, the filename of the .snd file (without drive, path, or
  32.    extension) is taken, and extensions of .sam and .not are attached.  For
  33.    the second and subsequent notes, a digit 2-9 or letter A-G is appended
  34.    to the filename, overwriting the last character of the filename if
  35.    necessary.  For example, if the following is entered (where piano.snd is
  36.    an instrument file with 3 notes defined):
  37.  
  38.        snd2sam a:piano
  39.  
  40.    The following files will be created in the current directory:
  41.  
  42.        piano.sam
  43.        piano.not
  44.        piano2.sam
  45.        piano2.not
  46.        piano3.sam
  47.        piano3.not
  48.  
  49.    For a note in an instrument file with pitch set, the .not file will
  50.    take the following format.  In this example, clarinet.snd has at
  51.    least one note defined, and note 1 is C3 in Sound.pdm (middle C, or
  52.    C2 in .mod pitch).
  53.  
  54.        Data for sample file clarinet.sam
  55.        Actual pitch at C2:  G1 finetune +1
  56.  
  57.        Tuning for ModEdit v.3.1:
  58.        Set tuning to:  G2
  59.        Transpose up 1 octave(s).
  60.  
  61.        Tuning for other editors:
  62.        Transpose up 5 semitone(s).
  63.  
  64.        Sample is looped.
  65.        Repeat start:  3828 (1914 words)
  66.        Repeat length:  1356 (678 words)
  67.  
  68.    The .not file begins with the name of the sample file it describes.  The
  69.    next line gives the actual pitch of the note (in .mod pitch) if played
  70.    back at period 428.  The pitch given here need not be a valid .mod
  71.    pitch; it can have a huge octave number, or even a negative one.
  72.  
  73.    The next few lines describe how to set the large-scale tuning in ModEdit
  74.    so that the pitch will be true (a C will be a C, a D-flat a D-flat, etc.)
  75.    and so that the range will be as large as possible (a large-scale tuning
  76.    in octave 2 is always selected).  Depending on the .snd file, it may be
  77.    necessary to transpose the sample's notes one or more octaves in either
  78.    direction.  In this example, the notes will sound one octave lower than
  79.    they are written when the tuning is set as indicated, so if working from
  80.    a musical score one must transpose them up one octave.
  81.  
  82.    Other .mod editors do not offer large-scale tuning; a note played at
  83.    period 428 is always displayed as C2.  In this case, the sample's notes
  84.    will sound 5 semitones lower than they are written, so if working from
  85.    a musical score one must transpose them up 5 semitones.
  86.  
  87.    The last part is the sample's looping information.  In the example, the
  88.    first note of clarinet.snd has the sustain region set, and this region
  89.    will be used as the repeat region for the sample.  The repeat start and
  90.    length are given in bytes (since ModEdit requires bytes) and in words
  91.    (which is what the .mod format requires).
  92.  
  93.    For new-format .snd files, the looping information is not given since I 
  94.    don't know where in the .snd header it is kept.
  95.  
  96.    For a sound file, or for a note in an instrument file with no pitch or
  97.    sustain set, the .not file will take the following format:
  98.  
  99.        Data for sample file meep.sam
  100.        No pitch set
  101.  
  102.        Sample is not looped.
  103.  
  104.    The .sam file will consist of a zero word followed by 8-bit signed PCM
  105.    samples.  If converted from a note with sustain set, the part of the
  106.    note after the sustain will be discarded (.snd notes have attack,
  107.    sustain, and decay, while .mod samples only have a beginning and a
  108.    looped section - no decay after the looping).
  109. *)
  110.  
  111. program snd2sam;
  112.  
  113. uses dos;
  114.  
  115. (*********************************************************************)
  116. (*************************** constants *******************************)
  117. (*********************************************************************)
  118.  
  119. const
  120.   maxsample =                (* maximum number of samples in a .sam file *)
  121.     131070;
  122.   bufsize =                  (* size of buffer for sound samples *)
  123.     32768;
  124.  
  125. (*********************************************************************)
  126. (***************************** types *********************************)
  127. (*********************************************************************)
  128.  
  129. type
  130.   noterec = record         (* needed fields from the .snd note record *)
  131.       valid:               (* true if note is set - needed in case some *)
  132.         boolean;           (*   notes must be skipped                   *)
  133.       pitch:               (* pitch of note at recording freqency; 1 = *)
  134.         byte;              (*   A-1 in .mod pitch; -1 if not set       *)
  135.       start_offset,        (* offset in .snd file of start of note data *)
  136.       length,              (* number of note samples *)
  137.       sustain_start,       (* start of sustain region - 0 if none *)
  138.       sustain_end:         (* end of sustain region - 0 if none *)
  139.         longint;
  140.         (* Array of pointers to note data on the heap - each pointer *)
  141.         (*   addresses at most bufsize bytes of sound data.  Notes   *)
  142.         (*   longer than 128k will be skipped.                       *)
  143.       data:
  144.         array [1..4] of pointer;
  145.     end; (* record *)
  146.  
  147.   notearray = array [1..16] of noterec;
  148.  
  149. (*********************************************************************)
  150. (************************ global variables ***************************)
  151. (*********************************************************************)
  152.  
  153. var
  154.   sndname,                   (* filename of input .snd file *)
  155.   basename,                  (* base name of .sam and .not files *)
  156.   currentdir,                (* current directory *)
  157.   outdir:                    (* directory for .sam and .not files *)
  158.     string;
  159.   numnotes,                  (* number of notes in the .snd file *)
  160.   note:                      (* note in the .snd file being converted *)
  161.     byte;
  162.   notelist:                  (* list of notes in the .snd file *)
  163.     notearray;
  164.   nextexit:                  (* next exit procedure in chain *)
  165.     pointer;
  166.  
  167. (*********************************************************************)
  168. (*************************** subroutines *****************************)
  169. (*********************************************************************)
  170.  
  171. procedure display_intro;
  172.   (*  This procedure displays an introductory message to the user.  *)
  173.  
  174. begin (* display_intro *)
  175.   writeln;
  176.   writeln( 'Snd2sam - DeskMate .snd to Amiga .mod sample conversion ',
  177.     'program' );
  178.   writeln;
  179. end; (* display_intro *)
  180.  
  181. (*********************************************************************)
  182.  
  183. function lastpos(
  184.   c:                         (* character to be searched for *)
  185.     char;
  186.   st:                        (* string to be searched *)
  187.     string ):
  188.       integer;
  189.   (*  This function returns the position of the last occurrence of c in
  190.       st, or 0 if it isn't there.  Same as the built-in pos() function,
  191.       but it starts at the other end of the string.  *)
  192.  
  193. var
  194.   place,                     (* position of character found *)
  195.   i:                         (* for looping over the characters *)
  196.     integer;
  197.  
  198. begin (* lastpos *)
  199.   place := 0;
  200.   for i := 1 to length( st ) do
  201.     if st[i] = c then
  202.       place := i;
  203.   lastpos := place;
  204. end; (* lastpos *)
  205.  
  206. (*********************************************************************)
  207.  
  208. procedure stop(
  209.   st1,                       (* first line to display *)
  210.   st2:                       (* second line to display *)
  211.     string );
  212.   (*  This procedure displays a 1- or 2-line message and halts the
  213.       program.  *)
  214.  
  215. begin (* stop *)
  216.   writeln( st1 );
  217.   if st2 <> '' then
  218.     writeln( st2 );
  219.   halt;
  220. end; (* stop *)
  221.  
  222. (*********************************************************************)
  223.  
  224. procedure process_command_line(
  225.   var sndname,               (* name of input .snd file *)
  226.       outdir:                (* directory for .sam and .not files *)
  227.         string );
  228.   (*  This procedure reads the command-line parameters and returns the
  229.       values above.  *)
  230.  
  231. var
  232.   dotpos:                    (* position of '.' in sndname *)
  233.     integer;
  234.  
  235. begin (* process_command_line *)
  236.     (* if no parameters (or more than 2), display syntax *)
  237.   if (paramcount = 0) or (paramcount > 2) then
  238.     stop( 'Syntax:',
  239.           '  SND2SAM <.snd file> [<output directory>]' );
  240.  
  241.     (* the first parameter is the input filename *)
  242.   sndname := paramstr( 1 );
  243.  
  244.     (* set input file extension to .snd if not specified *)
  245.   dotpos := lastpos( '.', sndname );
  246.   if dotpos = 0 then
  247.     sndname := sndname + '.snd';
  248.  
  249.     (* set the output directory *)
  250.   if paramcount = 1 then
  251.     outdir := '.'
  252.   else
  253.     outdir := paramstr( 2 );
  254. end; (* process_command_line *)
  255.  
  256. (*********************************************************************)
  257.  
  258. procedure readdata(
  259.   var sndfile:               (* file to read from *)
  260.     file;
  261.   var buffer;                (* buffer to read into *)
  262.   var nbytes:                (* on entry, number of bytes to read     *)
  263.     word );                  (* ... on exit, number successfully read *)
  264.   (*  This procedure encapsulates blockread(), halting the program on
  265.       file errors.  *)
  266.  
  267. var
  268.   result:                    (* number of bytes successfully read *)
  269.     word;
  270.  
  271. begin (* readdata *)
  272.   {$I-} blockread( sndfile, buffer, nbytes, result ); {$I+}
  273.   if IOResult <> 0 then
  274.     stop( 'Error reading input file - halting.', '' );
  275.   nbytes := result;
  276. end; (* readdata *)
  277.  
  278. (*********************************************************************)
  279.  
  280. procedure read_notedata(
  281.   sndname:                   (* name of input file, for messages *)
  282.     string;
  283.   var sndfile:               (* input .snd file *)
  284.     file;
  285.   var note:                  (* note, returned with sound read in *)
  286.     noterec );
  287.   (*  This procedure uses the data in the note record to read the sound
  288.       samples for the note from disk into a set of dynamically-allocated
  289.       buffers, returning pointers to the buffers.  Halts the program if
  290.       out of memory.  *)
  291.  
  292. var
  293.   bytesleft:                 (* number of bytes of sound data remaining *)
  294.     longint;
  295.   thistime:                  (* number of bytes read this pass *)
  296.     word;
  297.   i:                         (* for looping over the buffer pointers *)
  298.     integer;
  299.  
  300. begin (* read_notedata *)
  301.   with note do
  302.     begin
  303.  
  304.       (* if flagged invalid, just exit *)
  305.     if not valid then
  306.       exit;
  307.  
  308.       (* seek to start of note samples *)
  309.     {$I-} seek( sndfile, start_offset ); {$I+}
  310.     if IOResult <> 0 then
  311.       stop( 'Seek failed on file "' + sndname + '".', '' );
  312.  
  313.       (* read in the sample data *)
  314.     bytesleft := length;
  315.     i := 1;
  316.       (* while more sound data do: *)
  317.     while bytesleft > 0 do
  318.       begin
  319.         (* do bufsize bytes, or what's left, whichever is less *)
  320.       if bytesleft > bufsize then
  321.         thistime := bufsize
  322.       else
  323.         thistime := bytesleft;
  324.         (* adjust count of bytes remaining *)
  325.       bytesleft := bytesleft - thistime;
  326.         (* halt program if out of memory *)
  327.       if maxavail < thistime then
  328.         stop( 'Insufficient memory.', '' );
  329.         (* allocate a sound buffer *)
  330.       getmem( data[i], thistime );
  331.         (* read in sound data *)
  332.       readdata( sndfile, data[i]^, thistime );
  333.         (* go to next buffer *)
  334.       i := i + 1;
  335.       end; (* while more sound data *)
  336.  
  337.     end; (* with *)
  338. end; (* read_notedata *)
  339.  
  340. (*********************************************************************)
  341.  
  342. function is_newsnd(
  343.   sndname:                   (* name in input file *)
  344.     string ):
  345.       boolean;
  346.   (*  This function returns true if the input file is a new-format .snd 
  347.       file, or at least _not_ an old-format .snd file.  *)
  348.  
  349. var
  350.   sndfile:                   (* input file *)
  351.     file;
  352.   firstbyte:                 (* first byte of the file *)
  353.     byte;
  354.   IDtag:                     (* ID tag for new .snd file *)
  355.     array [0..1] of byte;
  356.   nbytes:                    (* number of bytes to read (1 or 2) *)
  357.     word;
  358.  
  359. begin (* is_newsnd *)
  360.     (* open the input file *)
  361.   assign( sndfile, sndname );
  362.   {$I-} reset( sndfile, 1 ); {$I+}
  363.     (* Note:  For some bizarre reason, reset() fails on read-only files. *)
  364.     (*   I decided to live with it (... and make my users live with it). *)
  365.   if IOResult <> 0 then
  366.     stop( 'Unable to open file:',
  367.           '  ' + sndname );
  368.  
  369.     (* if the file does not contain at least 46 bytes, it's not a new- 
  370.        format file (we verify the file size to keep from seeking or reading 
  371.        past the end of the file) *)
  372.   if filesize( sndfile ) < 46 then
  373.     begin
  374.     is_newsnd := false;
  375.     exit;
  376.     end;
  377.  
  378.     (* read the first byte of the file *)
  379.   nbytes := 1;
  380.   readdata( sndfile, firstbyte, nbytes );
  381.  
  382.     (* seek to the magic number *)
  383.   {$I-} seek( sndfile, 44 ); {$I+}
  384.   if IOResult <> 0 then
  385.     stop( 'Seek failed on file "' + sndname + '".', '' );
  386.  
  387.     (* read the ID tag *)
  388.   nbytes := 2;
  389.   readdata( sndfile, IDtag, nbytes );
  390.  
  391.     (* close the input file *)
  392.   close( sndfile );
  393.  
  394.     (* return true if ID is a match *)
  395.   is_newsnd := (firstbyte <> $1A) and (IDtag[0] = $1A) and (IDtag[1] = $80);
  396. end; (* is_newsnd *)
  397.  
  398. (*********************************************************************)
  399.  
  400. procedure read_newsnd(
  401.   sndname:                   (* name of input .snd file *)
  402.     string;
  403.   var numnotes:              (* number of notes in the file, returned *)
  404.     byte;
  405.   var notelist:              (* array of note information, returned *)
  406.     notearray );
  407.   (*  This procedure reads an entire new-format .snd file into memory and 
  408.       sets up the list of note information for the converter procedure.  *)
  409.  
  410. var
  411.   sndfile:                   (* input file *)
  412.     file;
  413.   nbytes:                    (* number of bytes read *)
  414.     word;
  415.   scratchst:                 (* scratch string *)
  416.     string;
  417.   i:                         (* for looping over the notes *)
  418.     integer;
  419.   nextnote:                  (* offset in file of next note record *)
  420.     longint;
  421.  
  422.     (* 114-byte fixed .snd header *)
  423.   fixedheader:
  424.     record
  425.       soundname:             (* ASCIIZ name of sound *)
  426.         packed array [1..10] of char;
  427.       unknown1:              (* (function unknown) *)
  428.         array [1..34] of byte;
  429.       IDtag:                 (* new .snd ID tag:  1Ah 80h *)
  430.         array [1..2] of byte;
  431.       numnotes,              (* number of notes in the file *)
  432.       instnum:               (* instrument number *)
  433.         word;
  434.       unknown2:              (* (function unknown) *)
  435.         array [1..16] of byte;
  436.       compression:           (* compression code *)
  437.         word;
  438.       unknown3:              (* (function unknown) *)
  439.         array [1..20] of byte;
  440.       rate:                  (* sampling rate in Hz *)
  441.         word;
  442.       unknown4:              (* (function unknown) *)
  443.         array [1..24] of byte;
  444.     end; (* record *)
  445.  
  446.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  447.  
  448.   procedure read_noteheader(
  449.     sndname:                 (* name of input file, for messages *)
  450.       string;
  451.     var sndfile:             (* input .snd file *)
  452.       file;
  453.     var note:                (* note information returned *)
  454.       noterec;
  455.     var nextnote:            (* offset in file of next note record, returned *)
  456.       longint );
  457.     (*  This procedure reads in a note record from the .snd header,
  458.         verifies the note data, and returns the note information.  *)
  459.  
  460.   var
  461.     nbytes:                  (* number of bytes read *)
  462.       word;
  463.  
  464.       (* 46-byte note record *)
  465.     noteheader:
  466.       record
  467.         nextnote:            (* offset in file of next sample descriptor *)
  468.           longint;
  469.         unknown1:            (* (function unknown) *)
  470.           array [1..2] of byte;
  471.         pitch,               (* pitch of note (see Newsnd.for) *)
  472.         unknown2,            (* (function unknown) *)
  473.         rangelo,             (* low limit of pitch range *)
  474.         rangehi:             (* high limit of pitch range *)
  475.           byte;
  476.         start,               (* start of note samples in file *)
  477.         length,              (* length of sample data, after compression *)
  478.                              (*   if any                                 *)
  479.         nsamples:            (* number of samples in note *)
  480.           longint;
  481.         unknown3:            (* (function unknown) *)
  482.           array [1..24] of byte;
  483.       end; (* record *)
  484.  
  485.   begin (* read_noteheader *)
  486.       (* read note record *)
  487.     nbytes := 46;
  488.     readdata( sndfile, noteheader, nbytes );
  489.     if nbytes <> 46 then
  490.       stop( 'Unexpected end-of-file reading .snd header of file:',
  491.             '  ' + sndname );
  492.  
  493.       (* verify note record *)
  494.     with noteheader do
  495.       begin
  496.         (* start out pessimistic *)
  497.       note.valid := false;
  498.         (* if not a valid pitch (or lack of one), skip *)
  499.       if not (pitch in [1..$3F,$FF]) then
  500.         begin
  501.         writeln( 'Note pitch invalid - note skipped.' );
  502.         exit;
  503.         end;
  504.         (* if file offset and length invalid, skip *)
  505.       if (start < 0) or (nsamples < 0) or (start+nsamples < 0) or
  506.         (start+nsamples > filesize( sndfile )) then
  507.         begin
  508.         writeln( '.snd file corrupt or truncated - note skipped.' );
  509.         exit;
  510.         end;
  511.         (* if too long, skip *)
  512.       if nsamples > maxsample then
  513.         begin
  514.         writeln( 'Note too long for .mod sample - note skipped.' );
  515.         exit;
  516.         end;
  517.         (* if no samples, skip *)
  518.       if nsamples = 0 then
  519.         begin
  520.         writeln( 'Note contains no sound data - note skipped.' );
  521.         exit;
  522.         end;
  523.       end; (* with *)
  524.  
  525.       (* copy data into record returned *)
  526.     note.valid := true;
  527.     note.pitch := noteheader.pitch;
  528.     note.start_offset := noteheader.start;
  529.     note.length := noteheader.nsamples;
  530.  
  531.       (* we don't know where the sustain interval is stored in the new file
  532.          format, so indicate that sustain is not set *)
  533.     note.sustain_start := 0;
  534.     note.sustain_end := 0;
  535.  
  536.       (* return pointer to next note record *)
  537.     nextnote := noteheader.nextnote;
  538.   end; (* read_noteheader *)
  539.  
  540.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  541.  
  542. begin (* read_newsnd *)
  543.     (* open the input file *)
  544.   assign( sndfile, sndname );
  545.   {$I-} reset( sndfile, 1 ); {$I+}
  546.   if IOResult <> 0 then
  547.     stop( 'Unable to open file:',
  548.           '  ' + sndname );
  549.  
  550.     (* read fixed header *)
  551.   nbytes := 114;
  552.   readdata( sndfile, fixedheader, nbytes );
  553.   if nbytes <> 114 then
  554.     stop( 'Unexpected end-of-file reading .snd header of file:',
  555.           '  ' + sndname );
  556.  
  557.     (* verify header data *)
  558.   with fixedheader do
  559.     begin
  560.       (* if no .snd signature, halt *)
  561.     if (IDtag[1] <> $1A) or (IDtag[2] <> $80) then
  562.       stop( 'File "' + sndname + '" is not an .snd file.', '' );
  563.       (* halt if invalid number of notes *)
  564.     if (numnotes = 0) or (numnotes > 16) then
  565.       stop( 'Invalid number of notes in file:',
  566.             '  ' + sndname );
  567.       (* if compressed, halt *)
  568.     if compression <> 0 then
  569.       stop( 'File "' + sndname + '" is compressed.',
  570.             'Use Sound.pdm to uncompress before converting.' );
  571.     end; (* with *)
  572.  
  573.     (* save number of notes *)
  574.   numnotes := fixedheader.numnotes;
  575.  
  576.     (* read the note headers *)
  577.   writeln( 'Reading note information ...' );
  578.   for i := 1 to numnotes do
  579.     begin
  580.     read_noteheader( sndname, sndfile, notelist[i], nextnote );
  581.     {$I-} seek( sndfile, nextnote ); {$I+}
  582.     if IOResult <> 0 then
  583.       stop( 'Seek failed on file "' + sndname + '".', '' );
  584.     end; (* for each note record *)
  585.  
  586.     (* read the note samples *)
  587.   writeln( 'Reading sample data ...' );
  588.   for i := 1 to numnotes do
  589.     read_notedata( sndname, sndfile, notelist[i] );
  590.  
  591.     (* close the input file *)
  592.   close( sndfile );
  593. end; (* read_newsnd *)
  594.  
  595. (*********************************************************************)
  596.  
  597. procedure read_oldsnd(
  598.   sndname:                   (* name of input .snd file *)
  599.     string;
  600.   var numnotes:              (* number of notes in the file, returned *)
  601.     byte;
  602.   var notelist:              (* array of note information, returned *)
  603.     notearray );
  604.   (*  This procedure reads the entire old-format .snd file into memory (an
  605.       .snd file must fit in RAM or Sound.pdm won't use it) and sets up the
  606.       list of note information for the converter procedure.  *)
  607.  
  608. var
  609.   sndfile:                   (* input .snd file *)
  610.     file;
  611.   nbytes:                    (* number of bytes read *)
  612.     word;
  613.   scratchst:                 (* scratch string *)
  614.     string;
  615.   i:                         (* for looping over the notes *)
  616.     integer;
  617.  
  618.     (* 16-byte fixed .snd header *)
  619.   fixedheader:
  620.     record
  621.       signature,             (* .snd signature byte 1Ah *)
  622.       compression,           (* compression code *)
  623.       numnotes,              (* number of notes in the file *)
  624.       instnum:               (* instrument number *)
  625.         byte;
  626.       instname:              (* name of instrument *)
  627.         packed array[1..10] of char;
  628.       rate:                  (* sampling rate in Hz *)
  629.         word;
  630.     end; (* record *)
  631.  
  632.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  633.  
  634.   procedure read_noteheader(
  635.     sndname:                 (* name of input file, for messages *)
  636.       string;
  637.     var sndfile:             (* input .snd file *)
  638.       file;
  639.     var note:                (* note information returned *)
  640.       noterec );
  641.     (*  This procedure reads in a note record from the .snd header,
  642.         verifies the note data, and returns the note information.  *)
  643.  
  644.   var
  645.     nbytes:                  (* number of bytes read *)
  646.       word;
  647.  
  648.       (* 28-byte note record *)
  649.     noteheader:
  650.       record
  651.         pitch,               (* pitch of note (see Snd.for) *)
  652.         pitchflag,           (* 0 = no pitch set, -1 = pitch set *)
  653.         rangelo,             (* low limit of pitch range *)
  654.         rangehi:             (* high limit of pitch range *)
  655.           byte;
  656.         start,               (* start of note samples in file *)
  657.         compressed_length,   (* length of compressed data, 0 if *)
  658.                              (*   uncompressed                  *)
  659.         unknown,             (* (function unknown) *)
  660.         nsamples,            (* number of samples in note *)
  661.         sustain_start,       (* start of sustain region *)
  662.         sustain_end:         (* end of sustain region *)
  663.           longint;
  664.       end; (* record *)
  665.  
  666.   begin (* read_noteheader *)
  667.       (* read note record *)
  668.     nbytes := 28;
  669.     readdata( sndfile, noteheader, nbytes );
  670.     if nbytes <> 28 then
  671.       stop( 'Unexpected end-of-file reading .snd header of file:',
  672.             '  ' + sndname );
  673.  
  674.       (* verify note record *)
  675.     with noteheader do
  676.       begin
  677.         (* start out pessimistic *)
  678.       note.valid := false;
  679.         (* if not a valid pitch (or lack of one), skip *)
  680.       if not (pitch in [1..$3F,$FF]) then
  681.         begin
  682.         writeln( 'Note pitch invalid - note skipped.' );
  683.         exit;
  684.         end;
  685.         (* if file offset and length invalid, skip *)
  686.       if (start < 0) or (nsamples < 0) or (start+nsamples < 0) or
  687.         (start+nsamples > filesize( sndfile )) then
  688.         begin
  689.         writeln( '.snd file corrupt or truncated - note skipped.' );
  690.         exit;
  691.         end;
  692.         (* if too long, skip *)
  693.       if nsamples > maxsample then
  694.         begin
  695.         writeln( 'Note too long for .mod sample - note skipped.' );
  696.         exit;
  697.         end;
  698.         (* if no samples, skip *)
  699.       if nsamples = 0 then
  700.         begin
  701.         writeln( 'Note contains no sound data - note skipped.' );
  702.         exit;
  703.         end;
  704.         (* if sustain region invalid, skip *)
  705.       if (sustain_start < 0) or (sustain_end < 0) or
  706.         (sustain_start > sustain_end) or (sustain_end > nsamples-1) then
  707.         begin
  708.         writeln( 'Invalid sustain region - note skipped.' );
  709.         exit;
  710.         end;
  711.       end; (* with *)
  712.  
  713.       (* copy data into record returned *)
  714.     note.valid := true;
  715.     note.pitch := noteheader.pitch;
  716.     note.start_offset := noteheader.start;
  717.     note.length := noteheader.nsamples;
  718.     note.sustain_start := noteheader.sustain_start;
  719.     note.sustain_end := noteheader.sustain_end;
  720.   end; (* read_noteheader *)
  721.  
  722.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  723.  
  724. begin (* read_oldsnd *)
  725.     (* open the input file *)
  726.   assign( sndfile, sndname );
  727.   {$I-} reset( sndfile, 1 ); {$I+}
  728.   if IOResult <> 0 then
  729.     stop( 'Unable to open file:',
  730.           '  ' + sndname );
  731.  
  732.     (* read fixed header *)
  733.   nbytes := 16;
  734.   readdata( sndfile, fixedheader, nbytes );
  735.   if nbytes <> 16 then
  736.     stop( 'Unexpected end-of-file reading .snd header of file:',
  737.           '  ' + sndname );
  738.  
  739.     (* verify header data *)
  740.   with fixedheader do
  741.     begin
  742.       (* if no .snd signature, halt *)
  743.     if signature <> $1A then
  744.       stop( 'File "' + sndname + '" is not an .snd file.', '' );
  745.       (* halt if invalid number of notes *)
  746.     if (numnotes = 0) or (numnotes > 16) then
  747.       stop( 'Invalid number of notes in file:',
  748.             '  ' + sndname );
  749.       (* if compressed, halt *)
  750.     if compression <> 0 then
  751.       stop( 'File "' + sndname + '" is compressed.',
  752.             'Use Sound.pdm to uncompress before converting.' );
  753.       (* if the sampling rate is invalid for an .snd file, halt *)
  754.     if (rate <> 5500) and (rate <> 11000) and (rate <> 22000) then
  755.       begin
  756.       str( rate, scratchst );
  757.       stop( 'Invalid sampling rate of ' + scratchst + ' in file:',
  758.             '  ' + sndname );
  759.       end; (* if bad sampling rate *)
  760.     end; (* with *)
  761.  
  762.     (* save number of notes *)
  763.   numnotes := fixedheader.numnotes;
  764.  
  765.     (* read the note headers *)
  766.   writeln( 'Reading note information ...' );
  767.   for i := 1 to numnotes do
  768.     read_noteheader( sndname, sndfile, notelist[i] );
  769.  
  770.     (* read the note samples *)
  771.   writeln( 'Reading sample data ...' );
  772.   for i := 1 to numnotes do
  773.     read_notedata( sndname, sndfile, notelist[i] );
  774.  
  775.     (* close the input file *)
  776.   close( sndfile );
  777. end; (* read_oldsnd *)
  778.  
  779. (*********************************************************************)
  780.  
  781. procedure get_basename(
  782.   sndname:                   (* name of input .snd file *)
  783.     string;
  784.   var basename:              (* base name of sample and note files *)
  785.     string );
  786.   (*  This procedure extracts the filename of the input .snd file, sans
  787.       drive, path, and extension, and returns it in basename.  *)
  788.  
  789. var
  790.   colonplace,                (* position of ':' in sndname *)
  791.   slashplace,                (* position of '\' in sndname *)
  792.   dotplace:                  (* position of '.' in sndname *)
  793.     integer;
  794.  
  795. begin (* get_basename *)
  796.     (* find where the drive and path end *)
  797.   colonplace := lastpos( ':', sndname );
  798.   slashplace := lastpos( '\', sndname );
  799.   if colonplace > slashplace then
  800.     slashplace := colonplace;
  801.  
  802.     (* delete the drive and path *)
  803.   delete( sndname, 1, slashplace );
  804.  
  805.     (* find extension *)
  806.   dotplace := lastpos( '.', sndname );
  807.  
  808.     (* return name without extension *)
  809.   if dotplace = 0 then
  810.     basename := sndname
  811.   else
  812.     basename := copy( sndname, 1, dotplace-1 );
  813. end; (* get_basename *)
  814.  
  815. (*********************************************************************)
  816.  
  817. function setname(
  818.   basename:                  (* base name to adjust *)
  819.     string;
  820.   note:                      (* note number *)
  821.     byte ):
  822.       string;
  823.   (*  This function returns the base name of the output .sam and .not
  824.       files, adjusted for the note number.  If the note number is not
  825.       1, a digit or letter is appended to the name to distinguish the
  826.       file, overwriting the last character of the name if necessary.  *)
  827.  
  828. var
  829.   notechar:                  (* character to be appended *)
  830.     char;
  831.  
  832. begin (* setname *)
  833.   if note <> 1 then
  834.     begin
  835.     if note < 10 then
  836.       notechar := chr( ord( '0' ) + note )
  837.     else
  838.       notechar := chr( ord( 'A' ) + note - 10 );
  839.     if length( basename ) = 8 then
  840.       basename[8] := notechar
  841.     else
  842.       basename := basename + notechar;
  843.     end; (* if name needs adjusting *)
  844.   setname := basename;
  845. end; (* setname *)
  846.  
  847. (*********************************************************************)
  848.  
  849. procedure do_note(
  850.   outdir,                    (* output directory, for messages *)
  851.   basename:                  (* base name of .sam and .not files *)
  852.     string;
  853.   note:                      (* note information record *)
  854.     noterec );
  855.   (*  This procedure converts a note from the .snd file into a .sam and
  856.       a .not file.  *)
  857.  
  858. var
  859.   samname,                   (* name of sample file *)
  860.   notname:                   (* name of note file *)
  861.     string;
  862.   samfile:                   (* .mod sample file *)
  863.     file;
  864.   notfile:                   (* note file *)
  865.     text;
  866.   loop_start,                (* start of looping region in sample *)
  867.   loop_length:               (* end of looping region in sample *)
  868.     longint;
  869.   is_looped:                 (* true if sample is looped *)
  870.     boolean;
  871.  
  872.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  873.  
  874.   procedure compute_loop(
  875.     var note:                (* note record whose looping information is to *)
  876.       noterec;               (*   be computed                               *)
  877.     var is_looped:           (* true if sample is looped *)
  878.       boolean;
  879.     var loop_start,          (* word offset in sample of start of loop *)
  880.         loop_length:         (* length in words of loop *)
  881.       longint );
  882.     (*  This procedure computes the loop start and length for an instrument
  883.         note with sustain set, adjusting the length of the note data to
  884.         discard the decay region.  is_looped returns false if the sample is
  885.         not looped.  *)
  886.  
  887.   var
  888.     wordlength,              (* length of sample data in words *)
  889.     loop_end:                (* word offset of end of loop *)
  890.       longint;
  891.  
  892.   begin (* compute_loop *)
  893.     with note do
  894.       begin
  895.         (* compute length in words, round length down *)
  896.       wordlength := length div 2;
  897.       length := wordlength * 2;
  898.  
  899.         (* compute loop_start, loop_end *)
  900.       loop_start := (sustain_start + 1) div 2;
  901.       loop_end := sustain_end div 2;
  902.       if loop_end > wordlength-1 then
  903.         loop_end := wordlength - 1;
  904.  
  905.         (* compute loop_length *)
  906.       loop_length := loop_end - loop_start + 1;
  907.  
  908.         (* if loop_length < 2, no loop *)
  909.       is_looped := loop_length >= 2;
  910.       if not is_looped then
  911.         exit;
  912.  
  913.         (* adjust loop_start and loop_end to account for zero word *)
  914.         (*   prepended to sample                                   *)
  915.       loop_start := loop_start + 1;
  916.       loop_end := loop_end + 1;
  917.  
  918.         (* adjust sample length to discard section after the loop *)
  919.       length := loop_end * 2;
  920.       end; (* with *)
  921.   end; (* compute_loop *)
  922.  
  923.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  924.  
  925.   procedure writedata(
  926.     var outfile:             (* file to written to *)
  927.       file;
  928.     var buffer;              (* buffer to write from *)
  929.     nbytes:                  (* number of bytes to write *)
  930.       word );
  931.     (*  This procedure encapsulates blockwrite(), halting the program on
  932.         file errors (including a full disk).  *)
  933.  
  934.   var
  935.     result:                  (* number of bytes successfully written *)
  936.       word;
  937.  
  938.   begin (* writedata *)
  939.     {$I-} blockwrite( outfile, buffer, nbytes, result ); {$I+}
  940.     if IOResult <> 0 then
  941.       stop( 'Error writing output file - halting.', '' );
  942.     if result <> nbytes then
  943.       stop( 'Disk full - halting.', '' );
  944.   end; (* writedata *)
  945.  
  946.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  947.  
  948.   procedure signum_convert(
  949.     buffer:                  (* pointer to data buffer *)
  950.       pointer;
  951.     nbytes:                  (* number of bytes in the buffer *)
  952.       word );
  953.     (*  This procedure converts a buffer full of unsigned 8-bit sound
  954.         data to signed.  *)
  955.  
  956.   begin (* signum_convert *)
  957.     inline( $1E/                       (*   PUSH    DS             *)
  958.             $9C/                       (*   PUSHF                  *)
  959.             $FC/                       (*   CLD                    *)
  960.             $C4/$BE/buffer/            (*   LES     DI,[BP+buffer] *)
  961.             $8C/$C0/                   (*   MOV     AX,ES          *)
  962.             $89/$FB/                   (*   MOV     BX,DI          *)
  963.             $81/$E7/$0F/$00/           (*   AND     DI,0Fh         *)
  964.             $B1/$04/                   (*   MOV     CL,4           *)
  965.             $D3/$EB/                   (*   SHR     BX,CL          *)
  966.             $01/$D8/                   (*   ADD     AX,BX          *)
  967.             $8E/$C0/                   (*   MOV     ES,AX          *)
  968.             $8E/$D8/                   (*   MOV     DS,AX          *)
  969.             $89/$FE/                   (*   MOV     SI,DI          *)
  970.             $8B/$8E/nbytes/            (*   MOV     CX,[BP+nbytes] *)
  971.                                        (* LOOPTOP:                 *)
  972.             $AC/                       (*   LODSB                  *)
  973.             $2C/$80/                   (*   SUB     AL,128         *)
  974.             $AA/                       (*   STOSB                  *)
  975.             $E2/$FA/                   (*   LOOP    LOOPTOP        *)
  976.             $9D/                       (*   POPF                   *)
  977.             $1F );                     (*   POP     DS             *)
  978.   end; (* signum_convert *)
  979.  
  980.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  981.  
  982.   procedure writesam(
  983.     var samfile:             (* output sample file *)
  984.       file;
  985.     note:                    (* note to write *)
  986.       noterec );
  987.     (*  This procedure converts the sound data from an .snd note to
  988.         signed and writes it out.  *)
  989.  
  990.   var
  991.     zeroword:                (* word of zero bits *)
  992.       word;
  993.     bytesleft:               (* bytes remaining to process *)
  994.       longint;
  995.     thistime:                (* bytes in this buffer *)
  996.       word;
  997.     i:                       (* for looping through data buffers *)
  998.       integer;
  999.  
  1000.   begin (* writesam *)
  1001.       (* write zero word *)
  1002.     zeroword := 0;
  1003.     writedata( samfile, zeroword, 2 );
  1004.  
  1005.       (* write sound data *)
  1006.     with note do
  1007.       begin
  1008.       i := 1;
  1009.       bytesleft := length;
  1010.         (* while more data do: *)
  1011.       while bytesleft > 0 do
  1012.         begin
  1013.           (* do bufsize bytes, or what's left, whichever is less *)
  1014.         if bytesleft > bufsize then
  1015.           thistime := bufsize
  1016.         else
  1017.           thistime := bytesleft;
  1018.           (* adjust count of bytes remaining *)
  1019.         bytesleft := bytesleft - thistime;
  1020.           (* convert signums *)
  1021.         signum_convert( data[i], thistime );
  1022.           (* write out the buffer *)
  1023.         writedata( samfile, data[i]^, thistime );
  1024.           (* go to next buffer *)
  1025.         i := i + 1;
  1026.         end; (* while more data *)
  1027.       end; (* with *)
  1028.   end; (* writesam *)
  1029.  
  1030.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  1031.  
  1032.   procedure writetext(
  1033.     var outfile:             (* text file to write to *)
  1034.       text;
  1035.     st:                      (* string to write *)
  1036.       string );
  1037.     (*  This procedure encapsulates writeln(), halting the program in
  1038.         case of disk errors.  *)
  1039.  
  1040.   begin (* writetext *)
  1041.     {$I-} writeln( outfile, st ); {$I+}
  1042.     if IOResult <> 0 then
  1043.       stop( 'Error writing output file - halting.', '' );
  1044.   end; (* writetext *)
  1045.  
  1046.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  1047.  
  1048.   procedure divmod(
  1049.     dividend,                (* number to divide *)
  1050.     divisor:                 (* number to divide by *)
  1051.       integer;
  1052.     var quotient,            (* quotient *)
  1053.         remainder:           (* remainder *)
  1054.       integer );
  1055.     (*  This procedure is a replacement for Pascal's div and mod operators.
  1056.         It returns a remainder that is always positive.  *)
  1057.  
  1058.   begin (* divmod *)
  1059.     remainder := dividend mod divisor;
  1060.     quotient := dividend div divisor;
  1061.     if remainder < 0 then
  1062.       if divisor < 0 then
  1063.         begin
  1064.         remainder := remainder - divisor;
  1065.         quotient := quotient + 1;
  1066.         end
  1067.       else (* divisor > 0 *)
  1068.         begin
  1069.         remainder := remainder + divisor;
  1070.         quotient := quotient - 1;
  1071.         end;
  1072.   end; (* divmod *)
  1073.  
  1074.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  1075.  
  1076.   function notestr(
  1077.     note:
  1078.       integer ):
  1079.         string;
  1080.     (*  This function returns a string representation of a note.  Note
  1081.         numbers are based on 0 = C2.  *)
  1082.  
  1083.   const
  1084.     letters: array[0..11] of string[2] =
  1085.       ( 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' );
  1086.  
  1087.   var
  1088.     pitch,                   (* pitch for note (C = 0, C# = 1, etc.) *)
  1089.     octave:                  (* octave for note (middle C = C2) *)
  1090.       integer;
  1091.     octavestr:               (* octave number as string *)
  1092.       string;
  1093.  
  1094.   begin (* notestr *)
  1095.       (* normalize to make 0 = C0 *)
  1096.     note := note + 24;
  1097.  
  1098.       (* set pitch and octave *)
  1099.     divmod( note, 12, octave, pitch );
  1100.  
  1101.       (* get octave as string *)
  1102.     str( octave, octavestr );
  1103.  
  1104.       (* return *)
  1105.     notestr := letters[pitch] + octavestr;
  1106.   end; (* notestr *)
  1107.  
  1108.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  1109.  
  1110.   procedure writenot(
  1111.     var notfile:             (* output note file *)
  1112.       text;
  1113.     samname:                 (* name of sample file *)
  1114.       string;
  1115.     note:                    (* note to describe *)
  1116.       noterec;
  1117.     is_looped:               (* true if sample is looped *)
  1118.       boolean;
  1119.     loop_start,              (* start of looping region (in words) *)
  1120.     loop_length:             (* length of looping region (in words) *)
  1121.       longint );
  1122.     (*  This procedure writes a text file describing a .mod sample (see
  1123.         top for format).  *)
  1124.  
  1125.   var
  1126.     numstr1,                 (* number as string, for output *)
  1127.     numstr2:                 (* another one *)
  1128.       string;
  1129.     modedtune,               (* tuning for ModEdit, 0 = C2 *)
  1130.     transpose,               (* transposition for ModEdit in octaves *)
  1131.     intpitch:                (* .mod pitch of note, 0 = C2 *)
  1132.       integer;
  1133.  
  1134.   begin (* writenot *)
  1135.       (* write name *)
  1136.     writetext( notfile, 'Data for sample file ' + samname );
  1137.  
  1138.       (* say if no pitch set *)
  1139.     if note.pitch = $FF then
  1140.       writetext( notfile, 'No pitch set' )
  1141.  
  1142.       (* pitch set:  display pitch and transposition *)
  1143.     else
  1144.       begin
  1145.  
  1146.         (* convert .snd pitch to .mod pitch and display *)
  1147.       intpitch := note.pitch - 33;
  1148.       writetext( notfile,
  1149.         'Actual pitch at C2:  ' + notestr( intpitch ) + ' finetune +1' );
  1150.  
  1151.         (* determine transposition for ModEdit v.3.1 *)
  1152.       divmod( intpitch, 12, transpose, modedtune );
  1153.  
  1154.         (* display tuning and transposition for ModEdit v.3.1 *)
  1155.       writetext( notfile, '' );
  1156.       writetext( notfile, 'Tuning for ModEdit v.3.1:' );
  1157.       writetext( notfile, 'Set tuning to:  ' + notestr( modedtune ) );
  1158.       if transpose < 0 then
  1159.         begin
  1160.         str( -transpose, numstr1 );
  1161.         writetext( notfile, 'Transpose up ' + numstr1 + ' octave(s).' )
  1162.         end (* if transposing up *)
  1163.       else if transpose = 0 then
  1164.         writetext( notfile, 'No transposition.' )
  1165.       else
  1166.         begin
  1167.         str( transpose, numstr1 );
  1168.         writetext( notfile, 'Transpose down ' + numstr1 + ' octave(s).' );
  1169.         end; (* if transposing down *)
  1170.  
  1171.         (* display transposition for other editors *)
  1172.       writetext( notfile, '' );
  1173.       writetext( notfile, 'Tuning for other editors:' );
  1174.       if intpitch < 0 then
  1175.         begin
  1176.         str( -intpitch, numstr1 );
  1177.         writetext( notfile, 'Transpose up ' + numstr1 + ' semitone(s).' )
  1178.         end (* if transposing up *)
  1179.       else if intpitch = 0 then
  1180.         writetext( notfile, 'No transposition.' )
  1181.       else
  1182.         begin
  1183.         str( intpitch, numstr1 );
  1184.         writetext( notfile, 'Transpose down ' + numstr1 + ' semitone(s).' );
  1185.         end; (* if transposing down *)
  1186.       end; (* if note set *)
  1187.  
  1188.       (* write looping information *)
  1189.     writetext( notfile, '' );
  1190.     if is_looped then
  1191.       begin
  1192.       writetext( notfile, 'Sample is looped.' );
  1193.       str( loop_start*2, numstr1 );
  1194.       str( loop_start, numstr2 );
  1195.       writetext( notfile,
  1196.         'Repeat start:  ' + numstr1 + ' (' + numstr2 + ' words)' );
  1197.       str( loop_length*2, numstr1 );
  1198.       str( loop_length, numstr2 );
  1199.       writetext( notfile,
  1200.         'Repeat length:  ' + numstr1 + ' (' + numstr2 + ' words)' );
  1201.       end (* if looped *)
  1202.     else
  1203.       writetext( notfile, 'Sample is not looped.' );
  1204.   end; (* writenot *)
  1205.  
  1206.   (*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*)
  1207.  
  1208. begin (* do_note *)
  1209.     (* if the note is not valid, just exit *)
  1210.   if not note.valid then
  1211.     exit;
  1212.  
  1213.     (* set names *)
  1214.   samname := basename + '.sam';
  1215.   notname := basename + '.not';
  1216.  
  1217.     (* compute looping information *)
  1218.   compute_loop( note, is_looped, loop_start, loop_length );
  1219.  
  1220.     (* open the .sam file *)
  1221.   assign( samfile, samname );
  1222.   {$I-} rewrite( samfile, 1 ); {$I+}
  1223.   if IOResult <> 0 then
  1224.     stop( 'Error creating file "' + samname + '" in directory',
  1225.           '"' + outdir + '" - halting.' );
  1226.  
  1227.     (* write and close the .sam file *)
  1228.   writeln( '  Writing file ', samname, ' ...' );
  1229.   writesam( samfile, note );
  1230.   close( samfile );
  1231.  
  1232.     (* open the .not file *)
  1233.   assign( notfile, notname );
  1234.   {$I-} rewrite( notfile ); {$I-}
  1235.   if IOResult <> 0 then
  1236.     stop( 'Error creating file "' + notname + '" in directory',
  1237.           '"' + outdir + '" - halting.' );
  1238.  
  1239.     (* write and close the .not file *)
  1240.   writeln( '  Writing file ', notname, ' ...' );
  1241.   writenot( notfile, samname, note, is_looped, loop_start,
  1242.     loop_length );
  1243.   close( notfile );
  1244. end; (* do_note *)
  1245.  
  1246. (*********************************************************************)
  1247. (************************** exit procedure ***************************)
  1248. (*********************************************************************)
  1249.  
  1250. {$F+} procedure mainexit; {$F-}
  1251.   (*  This procedure is executed automatically when the program exits for
  1252.       any reason.  It sets the current directory back to what it was when
  1253.       the program was invoked.  *)
  1254.  
  1255. begin (* mainexit *)
  1256.   chdir( currentdir );
  1257.   exitproc := nextexit;
  1258. end; (* mainexit *)
  1259.  
  1260. (*********************************************************************)
  1261. (*************************** main program ****************************)
  1262. (*********************************************************************)
  1263.  
  1264. begin (* snd2sam *)
  1265.     (* display intro message *)
  1266.   display_intro;
  1267.  
  1268.     (* get current directory and set up exit procedure *)
  1269.   getdir( 0, currentdir );
  1270.   nextexit := exitproc;
  1271.   exitproc := @mainexit;
  1272.  
  1273.     (* process the command-line parameters *)
  1274.   process_command_line( sndname, outdir );
  1275.  
  1276.     (* read in sound data *)
  1277.   if is_newsnd( sndname ) then
  1278.     read_newsnd( sndname, numnotes, notelist )
  1279.   else
  1280.     read_oldsnd( sndname, numnotes, notelist );
  1281.  
  1282.     (* change to sample directory *)
  1283.   {$I-} chdir( outdir ); {$I+}
  1284.   if IOResult <> 0 then
  1285.     stop( 'Invalid sample directory:',
  1286.           '  ' + outdir );
  1287.   writeln( 'Writing to directory "', outdir, '":' );
  1288.  
  1289.     (* set sample file name *)
  1290.   get_basename( sndname, basename );
  1291.  
  1292.     (* convert the notes to samples *)
  1293.   for note := 1 to numnotes do
  1294.     do_note( outdir, setname( basename, note ), notelist[note] );
  1295.  
  1296.     (* success *)
  1297.   writeln( 'Done.' );
  1298. end. (* snd2sam *)